#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 1994  Ling Li
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


import gobject, gtk, gtk.gdk
from gtk import *
from gtk import gdk

import string, re, os
from os import path
from urllib import unquote
from threading import Lock
from urlparse import urlsplit

from confirm_file_replace import *
from transfer_queue import *
from ftp_engine import *
from utils import *

class TransferView:

    COL_DIRECTION = 0          # An image used to show transfer direction
    COL_ICON = 1               # An image used to the type of the item
    COL_NAME = 2               # The name of the item
    COL_TARGET = 3             # The target place of the item
    COL_SIZE = 4               # The size of the item
    COL_REMARK = 5
    COL_DATA_DIRECTION = 6     # Should be DIRECTION_DOWN or DIRECTION_UP
    COL_DATA_SOURCE = 7        # The source url of the item
    COL_DATA_TARGET = 8        # The destination url of the item
    COL_DATA_SIZE = 9          # Store size in bytes
    COL_DATA_STATUS = 10       # Transfer status
    COL_DATA_SOURCE_SITE = 11
    COL_DATA_TARGET_SITE = 12
    
    DIRECTION_DOWN = 1         # Download, used in COL_DATA_DIRECTION
    DIRECTION_UP = 2           # Upload, used in COL_DATA_DIRECTION

    TRANSFER_STATUS_READY = 1  # The item is ready for transfer
    TRANSFER_STATUS_FAILED = 2 # The item is failed to transfer
    TRANSFER_STATUS_DOING = 3  # The item is being transfered now
    TRANSFER_STATUS_FINISHED = 4 # The transfer is finished
    TRANSFER_STATUS_SKIPPED = 5 # The item is skipped, the transfer continue
    
    __lock = Lock()
    trans_iter = None          # This point to item being transfering
    
    local_need_refresh = 0     # This means the local view should be
                               # refreshed after transfer
    remote_need_refresh = 0    # This means the remote view should be
                               # refreshed after transfer

    __selections = []
    __selected_count = 0

    __use_custom_queue_font = False
                               # whether use custom queue font
    __queue_font = ''          # custom font name and size
    __text_renderers = []

    def __init__(self, main_window):
        
        ACTIONS = {
            'transfer' : { 'update' : self.on_transfer_action_update,
                           'execute' : self.on_transfer_action_execute },
            'gotodir' : { 'update' : self.on_gotodir_action_update,
                          'execute' : self.on_gotodir_action_execute },
            'move_up' : { 'sensitive' : True,
                          'execute' : self.on_moveup_action_execute },
            'move_down' : { 'sensitive' : True,
                            'execute' : self.on_movedown_action_execute },
            'move_top' : { 'sensitive' : True,
                           'execute' : self.on_movetop_action_execute },
            'move_bottom' : { 'sensitive' : True,
                              'execute' : self.on_movebottom_action_execute},
            'save_queue' : { 'execute' : self.on_save_queue_action_execute},
            'load_queue' : { 'execute' : self.on_load_queue_action_execute},
            'restore_queue' : { 'sensitive' : False },
            'delete' : { 'sensitive' : True,
                         'execute' : self.on_delete_action_execute },
            'edit' : { 'sensitive' : False },
            'queueinfo' : { 'sensitive' : False },
            'clearqueue' : { 'sensitive' : True,
                             'update' : self.on_clearqueue_action_update ,
                             'execute' : self.on_clearqueue_action_execute },
            'freset' : {  'sensitive' : True,
                         'execute' : self.on_freset_action_execute },
            'fresetall' : { 'sensitive' : True,
                            'execute' : self.on_fresetall_action_execute },
            'fclearall' : { 'sensitive' : True,
                            'execute' : self.on_fclearall_action_execute },
            'fmark' : { 'sensitive' : True,
                        'execute' : self.on_fmark_action_execute },
            }

        MENU_ACTIONS = {
            'mi_transfer' : 'transfer',
            'mi_go_to_directory' : 'gotodir',
            'mi_edit' : 'edit',
            'mi_delete' : 'delete',
            'mi_move_up' : 'move_up',
            'mi_move_down' : 'move_down',
            'mi_move_to_top' : 'move_top',
            'mi_move_to_bottom' : 'move_bottom',
            'mi_failed_reset_selected' : 'freset',
            'mi_failed_reset_all' : 'fresetall',
            'mi_clear_all_failed' : 'fclearall',
            'mi_mark_as_failed' : 'fmark',
            'mi_queue_info' : 'queueinfo',
            'mi_restore_queue' : 'restore_queue',
            'mi_load_queue' : 'load_queue',
            'mi_save_queue' : 'save_queue',
            'mi_clear_queue' : 'clearqueue'
            }
        
        self.__main_window = main_window
        self.__config = main_window.coralftp.config
        self.__xml = main_window.xml
        self.__menu_xml = get_glade_xml('transfer_list_menu')
        self.__menu = self.__menu_xml.get_widget('transfer_list_menu')
        self.actions = ActionList(ACTIONS)
        
        self.__model = ListStore(gdk.Pixbuf,
                                 gdk.Pixbuf,
                                 gobject.TYPE_STRING,
                                 gobject.TYPE_STRING,
                                 gobject.TYPE_STRING,
                                 gobject.TYPE_STRING,
                                 gobject.TYPE_INT,
                                 gobject.TYPE_STRING,
                                 gobject.TYPE_STRING,
                                 gobject.TYPE_ULONG,
                                 gobject.TYPE_INT,
                                 gobject.TYPE_PYOBJECT,
                                 gobject.TYPE_PYOBJECT)
        self.__list = self.__xml.get_widget('tv_transfer_list')
        self.__list.set_model(self.__model)
        self.__list.connect('button-press-event',
                            self.on_list_button_press)
        self.__list.connect('button-release-event',
                            self.on_list_button_release)

        column = TreeViewColumn(_("Name"))
        renderer = CellRendererPixbuf()
        column.pack_start(renderer, expand=False)
        column.add_attribute(renderer, 'pixbuf', self.COL_DIRECTION)
        renderer = CellRendererPixbuf()
        column.pack_start(renderer, expand=False)
        column.add_attribute(renderer, 'pixbuf', self.COL_ICON)
        renderer = CellRendererText()
        self.__text_renderers.append(renderer)
        column.pack_start(renderer, expand=True)
        column.add_attribute(renderer, 'text', self.COL_NAME)
        column.set_resizable(True)
        self.__list.append_column(column)

        renderer = CellRendererText()
        self.__text_renderers.append(renderer)
        column = TreeViewColumn(_("Target"), renderer, text=self.COL_TARGET)
        column.set_resizable(True)
        self.__list.append_column(column)

        renderer = CellRendererText()
        self.__text_renderers.append(renderer)
        renderer.set_property('xalign', 1)
        column = TreeViewColumn(_("Size"), renderer, text=self.COL_SIZE)
        column.set_resizable(True)
        self.__list.append_column(column)

        renderer = CellRendererText()
        self.__text_renderers.append(renderer)
        column = TreeViewColumn(_("Remark"), renderer, text=self.COL_REMARK)
        column.set_resizable(True)
        self.__list.append_column(column)

        # setup fonts
        self.__use_custom_queue_font = config_value(
            self.__config, 'display', 'use_custom_queue_font')
        self.__queue_font = config_value(
            self.__config, 'display', 'queue_font')
        self.__change_font()
        self.__config.notify_add('display', 'use_custom_queue_font',
                                 self.on_use_custom_queue_font_change)
        self.__config.notify_add('display', 'queue_font',
                                 self.on_queue_font_change)

        # rules hint
        self.__list.set_property('rules-hint',
                                 config_value(self.__config, 'display',
                                              'rules_hint'))
        self.__config.notify_add(
            'display', 'rules_hint',
            lambda config, id, value, *args: \
                self.__list.set_property('rules-hint', value.get_bool()))

        # connect menu items with actions
        for widget_name, action_name in MENU_ACTIONS.items():
            widget = self.__menu_xml.get_widget(widget_name)
            self.actions[action_name].attach(widget)
        return

    def __count_selection(self, treemodel, path, iter, data):
        data.append(iter)

    def on_list_button_press(self, obj, event):
        self.__selections = []
        selection = self.__list.get_selection()
        selection.selected_foreach(self.__count_selection, self.__selections)
        self.__selected_count = len(self.__selections)
        return
        
    def on_list_button_release(self, obj, event):
        self.__selections = []
        selection = self.__list.get_selection()
        selection.selected_foreach(
            self.__count_selection, self.__selections)
        self.__selected_count = len(self.__selections)
        
        if event.button == 3:
            self.__menu.popup(None, None, None, 0, 0)
        return

    def add_to_queue(self, direction, source, target, size, source_site, \
                     target_site, iter=None):
        """Add new item to the transfer view. Remote path should in URL
        form.  size should be in bytes form. When finished, the iter of
        the new record is return."""

        if iter:
            iter = self.__model.insert_after(iter)
        else:
            iter = self.__model.append()
        self.__model.set_value(iter, self.COL_SIZE, size)
        if direction == TransferView.DIRECTION_UP:
            pixbuf = gdk.pixbuf_new_from_file(
                os.path.join(coralftp_datadir, 'put_arrow.png'))
        elif direction == TransferView.DIRECTION_DOWN:
            pixbuf = gdk.pixbuf_new_from_file(
                os.path.join(coralftp_datadir, 'get_arrow.png'))
        else:
            pixbuf = None
        self.__model.set_value(iter, self.COL_DIRECTION, pixbuf)
        self.__model.set_value(iter, self.COL_DATA_DIRECTION, direction)
        self.__model.set_value(iter, self.COL_DATA_SOURCE, source)
        self.__model.set_value(iter, self.COL_DATA_TARGET, target)
        self.__model.set_value(iter, self.COL_DATA_SIZE, size)
        self.__model.set_value(iter, self.COL_DATA_STATUS,
                               TransferView.TRANSFER_STATUS_READY)
        self.__model.set_value(iter, self.COL_DATA_SOURCE_SITE, source_site)
        self.__model.set_value(iter, self.COL_DATA_TARGET_SITE, target_site)
        if direction == self.DIRECTION_DOWN:
            (scheme, location, path, query, fragment) = urlsplit(source)
            path = string.replace(unquote(path), '//', '/')
            self.__model.set_value(iter, self.COL_NAME, path)
            self.__model.set_value(iter, self.COL_TARGET, target)
            self.__model.set_value(iter, self.COL_REMARK,
                                   'Download from %s' % location)
            self.__model.set_value(iter, self.COL_NAME, path)
        elif direction == self.DIRECTION_UP:
            (scheme, location, path, query, fragment) = urlsplit(target)
            path = string.replace(unquote(path), '//', '/')
            self.__model.set_value(iter, self.COL_NAME, source)
            self.__model.set_value(iter, self.COL_TARGET, path)
            self.__model.set_value(iter, self.COL_REMARK,
                                   'Upload to %s' % location)
        return iter

    def remove_from_queue(self, iter):
        self.__model.remove(iter)
        return

    def get_next(self):
        return self.__model.get_iter_first()

    def start_transfer(self, obj, data):
        """In this method, transfer_next is added to gtk idle sequence."""
        from main import MainWindow

        if self.__main_window.mode == MainWindow.MODE_FTP:
            self.orig_local_path = self.__main_window.local_view.current_path
            self.orig_remote_path = self.__main_window.remote_view.current_path
        self.trans_iter = self.__model.get_iter_first()
        
        # change main window status bar
        if self.__main_window.statusbar_type \
               == MainWindow.STATUSBAR_TYPE_GENERAL:
            self.__main_window.create_transfer_statusbar()

        # setup default option
        self.__show_upload_confirm_dialog = True
        self.__show_download_confirm_dialog = True
        self.__action_for_upload_conflict = RESPONSE_OVERWRITE
        self.__action_for_download_conflict = RESPONSE_RESUME
        gobject.idle_add(self.transfer_next)
        return

    def transfer_next(self):
        """
        This method find the next item require transfer and add self.transfer
        to gtk idle sequence. This method is always called through gtk idle.
        """
        
        # if the stuatus of current item is skipped or finished,
        # save it as original item
        status = self.__model.get_value(self.trans_iter,
                                        TransferView.COL_DATA_STATUS)
        if status == TransferView.TRANSFER_STATUS_FINISHED \
               or status == TransferView.TRANSFER_STATUS_SKIPPED or \
               status == TransferView.TRANSFER_STATUS_FAILED:
            oi = self.trans_iter
        elif status == TransferView.TRANSFER_STATUS_SKIPPED:
            oi = self.trans_iter
        else:
            oi = None

        # if the status of ftp_engine is ABORT, run finish_transfer
        rview = self.__main_window.remote_view
        if rview.ftp_engine != None \
               and rview.ftp_engine.status == STATUS_ABORT:
            self.finish_transfer()

        # find the next ready item
        while status != TransferView.TRANSFER_STATUS_READY:
            self.trans_iter = self.__model.iter_next(self.trans_iter)
            if self.trans_iter != None:
                status = self.__model.get_value(
                    self.trans_iter, TransferView.COL_DATA_STATUS)
            else:
                break
        if status == TransferView.TRANSFER_STATUS_READY:
            # change site if need
            direction = self.__model.get_value(
                self.trans_iter, TransferView.COL_DATA_DIRECTION)
            if direction == TransferView.DIRECTION_UP:
                site = self.__model.get_value(
                    self.trans_iter, TransferView.COL_DATA_TARGET_SITE)
            elif direction == TransferView.DIRECTION_DOWN:
                site = self.__model.get_value(
                    self.trans_iter, TransferView.COL_DATA_SOURCE_SITE)
            if site != self.__main_window.remote_view.site_info \
                  or self.__main_window.remote_view.ftp_engine == None:
                self.__main_window.remote_view.connect_site(site)

            # start real transfer method
            name = self.__model.get_value(
                self.trans_iter, TransferView.COL_NAME)
            if name[-1] == '/':
                gobject.idle_add(self.transfer_dir)
            else:
                gobject.idle_add(self.transfer_file)
        else:
            self.trans_iter = None

        # if the status of original item is skipped, change it back to ready
        # otherwise, delete the orginal item
        if oi != None:
            status = self.__model.get_value(oi, TransferView.COL_DATA_STATUS)
            if status == TransferView.TRANSFER_STATUS_SKIPPED:
                self.__model.set_value(oi, TransferView.COL_DATA_STATUS,
                                       TransferView.TRANSFER_STATUS_READY)
            elif status == TransferView.TRANSFER_STATUS_FINISHED:
                self.__model.remove(oi)
            elif status == TransferView.TRANSFER_STATUS_FAILED:
                pixbuf = gdk.pixbuf_new_from_file(
                    os.path.join(coralftp_datadir, 'failed.png'))
                self.__model.set_value(oi, TransferView.COL_DIRECTION,
                                       pixbuf)
            else:
                raise ValueError
        if self.trans_iter == None:
            self.finish_transfer()
        return False

    def finish_transfer(self):
        """When transfer finished, this method is called."""
        from main import MainWindow
        # restore local and remote view
        lview = self.__main_window.local_view
        rview = self.__main_window.remote_view
        if self.orig_local_path != lview.current_path:
            lview.set_path(self.orig_local_path)
        elif self.local_need_refresh:
            lview.refresh()
        if self.orig_remote_path != rview.current_path:
            rview.ftp_cd_path(self.orig_remote_path)
        elif self.remote_need_refresh:
            rview.ftp_refresh(1)
        # restore the status bar
        if self.__main_window.statusbar_type == MainWindow.STATUSBAR_TYPE_TRANSFER:
            self.__main_window.create_general_statusbar()
        return False

    def transfer_dir(self):
        """
        Upload or download directories.
        """
        from local_view import LocalView
        from remote_view import RemoteView

        self.__lock.acquire()
        if self.__main_window.mode == self.__main_window.MODE_FTP:
            lview = self.__main_window.local_view
            rview = self.__main_window.remote_view
            model = self.__model
            iter = self.trans_iter
            direction = model.get_value(iter,
                                        TransferView.COL_DATA_DIRECTION)
            name = model.get_value(iter, TransferView.COL_NAME)
            target = model.get_value(iter, TransferView.COL_TARGET)
            data_target = model.get_value(iter, TransferView.COL_DATA_TARGET)

            if direction == TransferView.DIRECTION_UP:
                ids = []
                def on_succeeded(*args):
                    for id in ids:
                        ftpe.disconnect(id)
                    lview.set_path(utf8_to_unicode(name))
                    lview.add_all_to_queue(iter, target)
                    model.set_value(iter, TransferView.COL_DATA_STATUS,
                                    TransferView.TRANSFER_STATUS_FINISHED)
                    gobject.idle_add(self.transfer_next)
                    self.__lock.release()

                def on_failed(*args):
                    for id in ids:
                        ftpe.disconnect(id)
                    model.set_value(iter, TransferView.COL_DATA_STATUS,
                                    TransferView.TRANSFER_STATUS_FAILED)
                    gobject.idle_add(tview.transfer_next)
                    self.__lock.release()

                # check existence, if not exist create one
                ftpe = rview.ftp_engine
                ids.append(ftpe.connect(
                    'interrupted', lambda *args:gobject.idle_add(on_failed, *args)))
                ids.append(ftpe.connect(
                    'listd-succeeded',
                    lambda *args:gobject.idle_add(on_succeeded, *args)))
                ids.append(ftpe.connect(
                    'listd-failed',
                    lambda *args:gobject.idle_add(ftpe.mkdir, target)))
                ids.append(ftpe.connect(
                    'mkdir-succeeded',
                    lambda *args:gobject.idle_add(on_succeeded)))
                ids.append(ftpe.connect(
                    'mkdir-failed',
                    lambda *args:gobject.idle_add(on_failed)))

                ftpe.listd(utf8_to_unicode(target))
                    
            elif direction == TransferView.DIRECTION_DOWN:
                failed = False
                # check existence of local dir
                if path.exists(target):
                    if not path.isdir(target):
                        failed = True
                else:
                    # create dir
                    try:
                        os.mkdir(target)
                    except OSError, (errno, errmsg):
                        failed = True
                if failed:
                    model.set_value(iter, TransferView.COL_DATA_STATUS,
                                    TransferView.TRANSFER_STATUS_FAILED)
                    gobject.idle_add(tview.transfer_next)
                    lock.release()
                else:
                    ids = []
                    def on_succeeded(obj, *args):
                        logging.debug('transfer_dir.down.on_succeded')
                        for id in ids:
                            rview.ftp_engine.disconnect(id)
                        rview.add_all_to_queue(iter, data_target)
                        gobject.idle_add(self.transfer_next)
                        model.set_value(iter, TransferView.COL_DATA_STATUS,
                                        TransferView.TRANSFER_STATUS_FINISHED)
                        self.__lock.release()
                    def on_failed(obj, *args):
                        logging.debug('transfer_dir.down.on_failed')
                        for id in ids:
                            rview.ftp_engine.disconnect(id)
                        gobject.idle_add(self.transfer_next)
                        model.set_value(iter, TransferView.COL_DATA_STATUS,
                                        TransferView.TRANSFER_STATUS_FAILED)
                        self.__lock.release()
                        
                    ids.append(rview.ftp_engine.connect(
                        'list-succeeded',
                        lambda *args:gobject.idle_add(on_succeeded, *args)))
                    ids.append(rview.ftp_engine.connect(
                        'cd-failed',
                        lambda *args:gobject.idle_add(on_failed, *args)))
                    ids.append(rview.ftp_engine.connect(
                        'list-failed',
                        lambda *args:gobject.idle_add(on_failed, *args)))
                    ids.append(rview.ftp_engine.connect(
                        'interrupted',
                        lambda *args:gobject.idle_add(on_failed, *args)))
                    rview.ftp_cd_path(utf8_to_unicode(name))
            else:
                self.__lock.release()
                raise ValueError

        return False

    def transfer_file(self):
        """
        Upload or download files.
        """
        from local_view import LocalView
        from remote_view import RemoteView

        self.__lock.acquire()
        if self.__main_window.mode == self.__main_window.MODE_FTP:
            lview = self.__main_window.local_view
            rview = self.__main_window.remote_view
            model = self.__model
            iter = self.trans_iter
            direction = model.get_value(iter, self.COL_DATA_DIRECTION)
            source = utf8_to_unicode(model.get_value(iter, self.COL_NAME))
            target = utf8_to_unicode(model.get_value(iter, self.COL_TARGET))
            data_target = model.get_value(iter, TransferView.COL_DATA_TARGET)
            
            # Upload
            if direction == TransferView.DIRECTION_UP:
                ids = []
                
                def step3(transfer_option):
                    if transfer_option == 1:
                        self.__model.set_value(
                            self.trans_iter, TransferView.COL_DATA_STATUS,
                            TransferView.TRANSFER_STATUS_DOING)
                        rview.ftp_engine.put(
                            source, local_target, 'b',
                            TransferRunningUpdater(self))
                        self.remote_need_refresh = 1
                    elif transfer_option == 0:
                        gobject.idle_add(self.transfer_next)
                    else:
                        # do nothing means cancel the transfer
                        self.finish_transfer()
                    self.__lock.release()
                    return

                def step2(obj, resp, *args):
                    if obj != None:
                        if not obj.ask_again:
                            self.__show_upload_confirm_dialog = False
                            self.__action_for_upload_conflict = resp
                        obj.destroy()
                    if resp == RESPONSE_CANCEL:
                        transfer_option = -1
                    elif resp == RESPONSE_CLOSE:
                        # close is considered as cancel
                        transfer_option = -1
                    elif resp == RESPONSE_OVERWRITE:
                        transfer_option = 1
                    elif resp == RESPONSE_SKIP:
                        transfer_option = 0
                        self.__model.set_value(
                            self.trans_iter,
                            TransferView.COL_DATA_STATUS,
                            TransferView.TRANSFER_STATUS_SKIPPED)
                    elif resp == RESPONSE_RESUME:
                        # resume is disabled
                        raise ValueError
                    elif resp == RESPONSE_RENAME:
                        p = self.__model.get_value(
                            self.trans_iter, TransferView.COL_TARGET)
                        p = '%s/%s' % (path.dirname(p),
                                       confirm_file_replace.new_name)
                        self.__model.set_value(
                            self.trans_iter, TransferView.COL_TARGET, p)
                        local_target = utf8_to_unicode(p)
                        transfer_option = 0
                    step3(transfer_option)
                    return

                # in order to wait for list finish, a function is defined here.
                def on_list_succeeded(obj):
                                        
                    for id in ids:
                        rview.ftp_engine.disconnect(id)
                    transfer_option = 1
                    # check exsiting
                    rmodel = rview.model
                    riter = rmodel.get_iter_first()
                    found = False
                    name1 = path.basename(
                        model.get_value(iter, TransferView.COL_TARGET))
                    while riter:
                        if name1 == rmodel.get_value(riter,
                                                     RemoteView.COL_NAME):
                            found = True
                            break
                        riter = rmodel.iter_next(riter)
                    if found:
                        if self.__show_upload_confirm_dialog:
                            info1 = rmodel.get_value(riter,
                                                     RemoteView.COL_INFO)
                            size1 = info1.size
                            date1 = rmodel.get_value(riter,
                                                     RemoteView.COL_DATE)
                            lmodel = lview.model
                            liter = lmodel.get_iter_first()
                            name2 = path.basename(
                                model.get_value(iter, self.COL_NAME))
                            while liter:
                                if name2 == lmodel.get_value(
                                    liter, LocalView.COL_FILENAME):
                                    break
                                liter = lmodel.iter_next(liter)
                                info2 = lmodel.get_value(
                                    liter, LocalView.COL_INFO)
                                size2 = info2.size
                                date2 = lmodel.get_value(
                                    liter, LocalView.COL_FILEMTIME)

                            # show a dialog box let user choose what to do
                            # with existing file
                            confirm_file_replace = ConfirmFileReplaceDialog(
                                self.__main_window.coralftp,
                                self.DIRECTION_UP,
                                name1, size1, date1, size2, date2)
                            confirm_file_replace.connect('response', step2)
                            confirm_file_replace.run()
                            return
                        else:
                            resp = self.__action_for_upload_conflict
                            step2(None, resp)
                    else:
                        step3(1)
                    return
                # end of on_list_succeeded

                # main part start
                local_target = target

                # change local view dir
                if lview.current_path != path.dirname(source):
                    lview.set_path(path.dirname(source))

                # for remote view, both server and path should be confirmed


                # change rview's path
                if rview.current_path != path.dirname(target): 
                    ids.append(rview.ftp_engine.connect(
                        'list-succeeded',
                        lambda *args:gobject.idle_add(on_list_succeeded, *args)))
                    rview.ftp_cd_path(path.dirname(target))
                else:
                    on_list_succeeded(None)

            # Download
            elif direction == self.DIRECTION_DOWN:
                ids = []

                def step3(transfer_option):
                    if transfer_option > 0:
                        self.__model.set_value(
                            self.trans_iter,
                            TransferView.COL_DATA_STATUS,
                            TransferView.TRANSFER_STATUS_DOING)
                        if transfer_option == 1:
                            # overwrite
                            rview.ftp_engine.get(
                                source, local_target, 'b', False,
                                TransferRunningUpdater(self))
                        elif transfer_option == 2:
                            # resume
                            rview.ftp_engine.get(
                                source, local_target, 'b', True,
                                TransferRunningUpdater(self))
                        self.local_need_refresh = 1
                    elif transfer_option == 0:
                        gobject.idle_add(self.transfer_next)
                    else:
                        self.finish_transfer()
                    self.__lock.release()
                    return

                def step2(obj, resp, *args):
                    if obj:
                        if not obj.ask_again:
                            self.__show_download_confirm_dialog = False
                            self.__action_for_download_conflict = resp
                        obj.destroy()
                    if resp == RESPONSE_CANCEL:
                        transfer_option = -1
                    elif resp == RESPONSE_CLOSE:
                        # close is considered as cancel
                        transfer_option = -1
                    elif resp == RESPONSE_OVERWRITE:
                        transfer_option = 1
                    elif resp == RESPONSE_SKIP:
                        transfer_option = 0
                        self.__model.set_value(
                            self.trans_iter,
                            TransferView.COL_DATA_STATUS,
                            TransferView.TRANSFER_STATUS_SKIPPED)
                    elif resp == RESPONSE_RESUME:
                        transfer_option = 2
                    elif resp == RESPONSE_RENAME:
                        p = self.__model.get_value(
                            self.trans_iter, TransferView.COL_TARGET)
                        p = '%s/%s' % (path.dirname(p),
                                       confirm_file_replace.new_name)
                        self.__model.set_value(
                            self.trans_iter, TransferView.COL_TARGET, p)
                        local_target = utf8_to_unicode(p)
                        transfer_option = 0
                    step3(transfer_option)
                    return
                    
                def on_list_succeeded(*args):
                    for id in ids:
                        rview.ftp_engine.disconnect(id)
                    transfer_option = 1
                    # check exsiting
                    lmodel = lview.model
                    liter = lmodel.get_iter_first()
                    found = 0
                    name1 = path.basename(
                        model.get_value(iter, TransferView.COL_TARGET))
                    while liter:
                        if name1 == lmodel.get_value(liter,
                                                     LocalView.COL_FILENAME):
                            found = 1
                            break
                        liter = lmodel.iter_next(liter)
                    if found:
                        if self.__show_download_confirm_dialog:
                            info1 = lmodel.get_value(liter,
                                                     LocalView.COL_INFO)
                            size1 = info1.size
                            date1 = lmodel.get_value(liter,
                                                     LocalView.COL_FILEMTIME)
                            rmodel = rview.model
                            riter = rmodel.get_iter_first()
                            name2 = path.basename(
                                model.get_value(iter, self.COL_NAME))
                            size2 = 0
                            date2 = None
                            while riter:
                                if name2 == rmodel.get_value(
                                    riter, RemoteView.COL_NAME):
                                    break
                                riter = rmodel.iter_next(riter)
                                info2 = rmodel.get_value(riter,
                                                         RemoteView.COL_INFO)
                                size2 = info2.size
                                date2 = rmodel.get_value(riter,
                                                         RemoteView.COL_DATE)
                                
                            # show a dialog box let user choose what to do
                            # with existing file
                            confirm_file_replace = ConfirmFileReplaceDialog(
                                self.__main_window.coralftp,
                                self.DIRECTION_DOWN,
                                name1, size1, date1, size2, date2)
                            confirm_file_replace.connect('response', step2)
                            confirm_file_replace.run()
                        else:
                            resp = self.__action_for_download_conflict
                            step2(None, resp)
                    else:
                        step3(1)
                    return
                # end of on_list_succeeded
                            
                # main part start
                local_target = target
                # change dir
                if lview.current_path != path.dirname(target):
                    lview.set_path(path.dirname(target))
                if rview.current_path != path.dirname(source):
                    ids.append(rview.ftp_engine.connect(
                        'list-succeeded',
                        lambda *args:gobject.idle_add(on_list_succeeded, *args)))
                    rview.ftp_cd_path(path.dirname(source))
                else:
                    on_list_succeeded()

            # other direction
            else:
                self.__lock.release()

        return False

    def on_transfer_succeeded(self, obj):
        from main import MainWindow
        self.model.set_value(self.trans_iter,
                             TransferView.COL_DATA_STATUS,
                             TransferView.TRANSFER_STATUS_FINISHED)
        gobject.idle_add(self.transfer_next)
        return

    def on_transfer_failed(self, obj, errno, errmsg):
        self.model.set_value(self.trans_iter,
                             TransferView.COL_DATA_STATUS,
                             TransferView.TRANSFER_STATUS_FAILED)
        if errno == ERRNO_ABORT:
            gobject.idle_add(self.finish_transfer)
        else:
            gobject.idle_add(self.transfer_next)
        return

    def __getattr__(self, name):
        if name == 'model':
            return self.__list.get_model()
        elif name == 'main_window':
            return self.__main_window
        else:
            raise AttributeError, name

    def on_transfer_action_update(self, obj):
        obj.set_property('sensitive', self.__model.iter_n_children(None) > 0)
        return

    def on_transfer_action_execute(self, obj, *args):
        self.start_transfer(obj, None)
        return

    def on_gotodir_action_update(self, obj):
        obj.set_property(
            'sensitive', self.__selected_count == 1 \
            and self.__main_window.left_view.is_ready() \
            and (self.__main_window.right_view.is_ready() \
                 or self.__main_window.right_view.ftp_engine == None))
        return

    def on_gotodir_action_execute(self, obj, *args):
        iter = self.__selections[0]
        direction = self.__model.get_value(iter,
                                           TransferView.COL_DATA_DIRECTION)
        source = self.__model.get_value(iter, TransferView.COL_DATA_SOURCE)
        target = self.__model.get_value(iter, TransferView.COL_DATA_TARGET)
        source_site = self.__model.get_value(
            iter, TransferView.COL_DATA_SOURCE_SITE)
        target_site = self.__model.get_value(
            iter, TransferView.COL_DATA_TARGET_SITE)
        if direction == TransferView.DIRECTION_UP:
            self.__main_window.local_view.set_path(os.path.dirname(source))
            if self.__main_window.remote_view.site_info != target_site:
                self.__main_window.remote_view.connect_site(target_site)
            m = ftpurl_parse(target)
            remote_path = None
            if m != None:
                username, password, server, port, path = m
                if path and len(path) > 0:
                    if path[-1] == '/':
                        remote_path = path
                    else:
                        pos = string.rfind('/')
                        if pos >= 0:
                            remote_path = path[:pos]
            if remote_path:
                self.__main_window.remote_view.ftp_cd_path(remote_path)
        elif direction == TransferView.DIRECTION_DOWN:
            self.__main_window.local_view.set_path(os.path.dirname(target))
            if self.__main_window.remote_view.site_info != source_site:
                self.__main_window.remote_view.connect_site(source_site)
            m = ftpurl_parse(source)
            remote_path = None
            if m != None:
                username, password, server, port, path = m
                if path and len(path) > 0:
                    if path[-1] == '/':
                        remote_path = path
                    else:
                        pos = string.rfind('/')
                        if pos >= 0:
                            remote_path = path[:pos]
            if remote_path:
                self.__main_window.remote_view.ftp_cd_path(remote_path)
        return

    def on_moveup_action_execute(self, obj, *args):
        if self.__selected_count == 0: return
        selected_path = []
        for iter in self.__selections:
            (path,) = self.__model.get_path(iter)
            selected_path.append(path)
        selected_path.sort()

        new_order = []
        new_order.extend(range(self.__list.iter_n_children(None)))
        for path in selected_path:
            if path == 0: continue
            t = new_order[path-1]
            new_order[path-1] = new_order[path]
            new_order[path] = t
        return

    def on_movedown_action_execute(self, obj, *args):
        if self.__selected_count == 0: return
        selected_path = []
        for iter in self.__selections:
            (path,) = self.__model.get_path(iter)
            selected_path.append(path)
        selected_path.sort()
        selected_path.reverse()
        
        children_num = self.__list.iter_n_children(None)
        new_order = []
        new_order.extend(range(children_num))
        for path in selected_path:
            if path >= children_num - 1: continue
            t = new_order[path]
            new_order[path] = new_order[path+1]
            new_order[path+1] = t
        return

    def on_movetop_action_execute(self, obj, *args):
        if self.__selected_count == 0: return
        selected_path = []
        for iter in self.__selections:
            (path,) = self.__model.get_path(iter)
            selected_path.append(path)
        selected_path.sort()

        new_order = []
        new_order.extend(range(self.__model.iter_n_children(None)))
        first = 0
        for path in selected_path:
            for i in range(first, path):
                new_order[i] = new_order[i] + 1
            new_order[path] = first
            first = first + 1
        return

    def on_movebottom_action_execute(self, obj, *args):
        if self.__selected_count == 0: return
        selected_path = []
        for iter in self.__selections:
            (path,) = self.__model.get_path(iter)
            selected_path.append(path)
        selected_path.sort()

        children_num = self.__model.iter_n_children(None)
        new_order = []
        new_order.extend(range(children_num))
        last = children_num - 1
        for path in selected_path:
            for i in range(path, last):
                new_order[i] = new_order[i] - 1
            new_order[path] = last
            last = last - 1
        return

    def on_delete_action_execute(self, obj, *args):
        for iter in self.__selections:
            status = self.__model.get_value(iter,
                                            TransferView.COL_DATA_STATUS)
            if status in (TransferView.TRANSFER_STATUS_READY,
                          TransferView.TRANSFER_STATUS_FAILED,
                          TransferView.TRANSFER_STATUS_SKIPPED):
                self.__model.remove(iter)
        return

    def on_clearqueue_action_update(self, obj):
        obj.set_property('sensitive', self.__model.iter_n_children(None) > 0)
        return
    
    def on_clearqueue_action_execute(self, obj, *args):
        self.__model.clear()
        return

    def on_freset_action_execute(self, obj, *args):
        for iter in self.__selections:
            status = self.__model.get_value(iter,
                                            TransferView.COL_DATA_STATUS)
            if status == TransferView.TRANSFER_STATUS_FAILED:
                direction = self.__model.get_value(iter,
                                                   TransferView.COL_DIRECTION)
                if direction == TransferView.DIRECTION_UP:
                    pixbuf = gdk.pixbuf_new_from_file(
                        os.path.join(coralftp_datadir, 'put_arrow.png'))
                elif direction == TransferView.DIRECTION_DOWN:
                    pixbuf = gdk.pixbuf_new_from_file(
                        os.path.join(coralftp_datadir, 'get_arrow.png'))
                self.__model.set_value(iter, TransferView.COL_DATA_STATUS,
                                       TransferView.TRANSFER_STATUS_READY)
        return

    def on_fresetall_action_execute(self, obj, *args):
        iter = self.__model.get_iter_first()
        while iter != None:
            status = self.__model.get_value(iter,
                                            TransferView.COL_DATA_STATUS)
            if status == TransferView.TRANSFER_STATUS_FAILED:
                direction = self.__model.get_value(iter,
                                                   TransferView.COL_DIRECTION)
                if direction == TransferView.DIRECTION_UP:
                    pixbuf = gdk.pixbuf_new_from_file(
                        os.path.join(coralftp_datadir, 'put_arrow.png'))
                elif direction == TransferView.DIRECTION_DOWN:
                    pixbuf = gdk.pixbuf_new_from_file(
                        os.path.join(coralftp_datadir, 'get_arrow.png'))
                self.__model.set_value(iter, TransferView.COL_DATA_STATUS,
                                       TransferView.TRANSFER_STATUS_READY)
            iter = self.__model.iter_next(iter)
        return

    def on_fclearall_action_execute(self, obj, *args):
        iter = self.__model.get_iter_first()
        while iter != None:
            status = self.__model.get_value(iter,
                                            TransferView.COL_DATA_STATUS)
            if status == TransferView.TRANSFER_STATUS_FAILED:
                self.__model.remove(iter)
            iter = self.__model.iter_next(iter)
        return

    def on_save_queue_action_execute(self, obj, *args):
        dlg = FileChooserDialog(
            title = _("Save queue as"),
            parent = self.__main_window.widget,
            action = FILE_CHOOSER_ACTION_SAVE,
            buttons = (STOCK_CANCEL, RESPONSE_CANCEL,
                       STOCK_SAVE, RESPONSE_OK))
        from time import gmtime, strftime
        filename = strftime('%Y-%m-%d_%H.%M.%S.cfq')
        dlg.set_current_name(filename)
        filter = FileFilter()
        filter.set_name(_("CoralFTP queue"))
        filter.add_pattern('*.cfq')
        dlg.add_filter(filter)
        filter = FileFilter()
        filter.set_name(_("All files"))
        filter.add_pattern('*')
        dlg.add_filter(filter)
        resp = dlg.run()
        if resp == RESPONSE_OK:
            filename = dlg.get_filename()
            queue = []
            iter = self.__model.get_iter_first()
            while iter:
                status = self.__model.get_value(
                    iter, TransferView.COL_DATA_STATUS)
                if status != TransferView.TRANSFER_STATUS_FINISHED:
                    item = {}
                    item['direction'] = self.__model.get_value(
                        iter, TransferView.COL_DATA_DIRECTION)
                    item['source'] = self.__model.get_value(
                        iter, TransferView.COL_DATA_SOURCE)
                    item['target'] = self.__model.get_value(
                        iter, TransferView.COL_DATA_TARGET)
                    item['size'] = self.__model.get_value(
                        iter, TransferView.COL_DATA_SIZE)
                    site = self.__model.get_value(
                        iter, TransferView.COL_DATA_SOURCE_SITE)
                    if site != None:
                        item['source_site_id'] = site['id']
                    else:
                        item['source_site_id'] = ''
                    site = self.__model.get_value(
                        iter, TransferView.COL_DATA_TARGET_SITE)
                    if site != None:
                        item['target_site_id'] = site['id']
                    else:
                        item['target_site_id'] = ''
                    queue.append(item)
                    iter = self.__model.iter_next(iter)
            transfer_queue = TransferQueue(queue)
            transfer_queue.save_to_file(filename)
        dlg.destroy()
        return

    def on_load_queue_action_execute(self, obj, *args):
        dlg = FileChooserDialog(
            title = _("Load queue"),
            parent = self.__main_window.widget,
            action = FILE_CHOOSER_ACTION_SAVE,
            buttons = (STOCK_CANCEL, RESPONSE_CANCEL,
                       STOCK_OPEN, RESPONSE_OK))
        filter = FileFilter()
        filter.set_name(_("CoralFTP queue"))
        filter.add_pattern('*.cfq')
        dlg.add_filter(filter)
        filter = FileFilter()
        filter.set_name(_("All files"))
        filter.add_pattern('*')
        dlg.add_filter(filter)
        resp = dlg.run()
        if resp == RESPONSE_OK:
            filename = dlg.get_filename()
            transfer_queue = TransferQueue()
            transfer_queue.load_from_file(filename)
            for item in transfer_queue:
                direction = string.atoi(item['direction'])
                source = item['source']
                target = item['target']
                sm = self.__main_window.coralftp.get_site_manager()
                if item['source_site_id'] != '':
                    source_site = sm.get_site_by_id(item['source_site_id'])
                else:
                    source_site = None
                if item['target_site_id'] != '':
                    target_site = sm.get_site_by_id(item['target_site_id'])
                else:
                    target_site = None
                size = string.atoi(item['size'])
                self.add_to_queue(direction, source, target, size,
                                  source_site, target_site)
        dlg.destroy()
        return

    def on_fmark_action_execute(self, obj, *args):
        for iter in self.__selections:
            status = self.__model.get_value(iter,
                                            TransferView.COL_DATA_STATUS)
            pixbuf = gdk.pixbuf_new_from_file(
                os.path.join(coralftp_datadir, 'failed.png'))
            if status in (TransferView.TRANSFER_STATUS_READY,
                          TransferView.TRANSFER_STATUS_SKIPPED):
                self.__model.set_value(iter, TransferView.COL_DATA_STATUS,
                                       TransferView.TRANSFER_STATUS_FAILED)
                self.__model.set_value(iter, TransferView.COL_DIRECTION,
                                       pixbuf)
        return

    def on_use_custom_queue_font_change(self, config, id, value, *args):
        self.__use_custom_queue_font = value.get_bool()
        self.__change_font()
        return

    def on_queue_font_change(self, config, id, value, *args):
        self.__queue_font = value.get_string()
        self.__change_font()
        return

    def __change_font(self):
        for renderer in self.__text_renderers:
            if self.__use_custom_queue_font:
                renderer.set_property('font', self.__queue_font)
            else:
                renderer.set_property('font', None)
        return

class TransferRunningUpdater:

    __start_time = None
    start_bytes = 0

    def __init__(self, tview):
        self.__tview = tview
        self.__main_window = tview.main_window

    def __call__(self, bytes, msg):
        if self.__start_time == None:
            self.__start_time = time.time()
        from main import MainWindow
        model = self.__tview.model
        iter = model.get_iter_first()
        direction = model.get_value(iter, TransferView.COL_DATA_DIRECTION)
        filename = model.get_value(iter, TransferView.COL_NAME)
        size = model.get_value(iter, TransferView.COL_DATA_SIZE)
        if direction == TransferView.DIRECTION_UP:
            msg1 = _("Uploading %s, %s")
        elif direction == TransferView.DIRECTION_DOWN:
            msg1 = _("Downloading %s, %s")
        else:
            raise ValueError
        self.__main_window.status1.set_text(
            msg1 % (path.basename(filename), msg))
        if size != 0:
            self.__main_window.status2.set_fraction(float(bytes) / size)
            percent = int(float(bytes) / size * 100)
        else:
            percent = 0
        elapsed = time.time() - self.__start_time
        speed = (bytes - self.start_bytes) / elapsed
        remaining = (size - bytes) / speed
        elapsed = (int(elapsed / 60), int(elapsed % 60))
        remaining = (int(remaining / 60), int(remaining % 60))
        self.__main_window.status2.set_text(
            '%s(%sps)' % (format_size('Auto', bytes),
                        format_size('Auto', speed)))
        self.__main_window.status3.set_text('%d%%' % percent)
        self.__main_window.status4.set_text(_("Elapsed: %02d:%02d") % elapsed)
        self.__main_window.status5.set_text(_("Remaining: %02d:%02d") % remaining)
        return

